﻿#region --- License & Copyright Notice ---
/*
ConsoleFx CommandLine Processing Library

Copyright (c) 2006-2012 Jeevan James
All rights reserved.

The contents of this file are made available under the terms of the
Eclipse Public License v1.0 (the "License") which accompanies this
distribution, and is available at the following URL:
http://opensource.org/licenses/eclipse-1.0.txt

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

By using this software in any fashion, you are agreeing to be bound by the
terms of the License.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using ConsoleFx.Parsers;
using ConsoleFx.Programs.Interactive.Commands;
using ConsoleFx.Programs.Interactive.Resources;

namespace ConsoleFx.Programs.Interactive
{
    public sealed class InteractiveShell : Parser
    {
        private string _exitKeyword = ShellMessages.ExitKeyword;
        private string _prompt = "> ";

        private readonly RegisteredCommandCollection _registeredCommands = new RegisteredCommandCollection();

        private ConsoleColor _originalForeColor;
        private ConsoleColor _originalBackColor;

        public InteractiveShell()
        {
            Assembly currentAssembly = Assembly.GetEntryAssembly();
            _registeredCommands.AddRange(currentAssembly.GetTypes()
                .Where(type => typeof(CommandBase).IsAssignableFrom(type))
                .Where(type => type.Name.EndsWith("Command", StringComparison.OrdinalIgnoreCase)));
        }

        public void Run()
        {
            _originalForeColor = Console.ForegroundColor;
            _originalBackColor = Console.BackgroundColor;

            if (ClearScreenOnStart)
            {
                if (ForeColor.HasValue)
                    Console.ForegroundColor = ForeColor.Value;
                if (BackColor.HasValue)
                    Console.BackgroundColor = BackColor.Value;
                Console.Clear();
            }

            if (OnStartup != null)
                OnStartup(this);

            //Start a loop that keeps prompting the user for input until they type the exit command
            string input = ShellPrompt();
            while (!input.Equals(ExitKeyword, StringComparison.OrdinalIgnoreCase))
            {
                if (string.IsNullOrWhiteSpace(input))
                {
                    input = ShellPrompt();
                    continue;
                }

                //Tokenize the input and consider the first token to be the command
                IEnumerable<string> tokens = Tokenizer.Tokenize(input);
                string firstToken = tokens.First();

                //Try and locate the specified command in the list of registered commands.
                //If not found, display an error message and show the prompt again.
                RegisteredCommand registeredCommand = _registeredCommands[firstToken];
                if (registeredCommand == null)
                {
                    ConsoleEx.WriteLine(ConsoleColor.Red, null, ShellMessages.CommandNotRecognized, firstToken);
                    input = ShellPrompt();
                    continue;
                }
                Type commandType = registeredCommand.Type;

                //Reset all the specified and available properties in the parser's Properties property
                ResetProperties();

                //Create an instance of the command class. If a factory is specified, use that, otherwise
                //use Activator.CreateInstance (in which case the command class must have a default
                //constructor
                var command = registeredCommand.Factory != null ? registeredCommand.Factory() : (CommandBase)Activator.CreateInstance(commandType);

                //Retrieve arguments and options from the command and add them to the parser properties
                Properties.Available.Arguments.AddRange(ProgramContext.Normal, command.GetArguments());
                foreach (ShellOption option in command.GetOptions())
                    Properties.Available.Options.Add(option);

                try
                {
                    Parse(tokens);
                    if (SpaceBeforeCommand)
                        Console.WriteLine();
                    command.Execute();
                    if (SpaceAfterCommand)
                        Console.WriteLine();
                }
                catch (Exception ex)
                {
                    ConsoleEx.WriteLine(ConsoleColor.Red, null, ex.Message);
                }

                input = ShellPrompt();
            }

            Console.ForegroundColor = _originalForeColor;
            Console.BackgroundColor = _originalBackColor;
            if (ClearScreenOnEnd)
                Console.Clear();
        }

        private string ShellPrompt()
        {
            ConsoleEx.Write(PromptForeColor, PromptBackColor, Prompt);

            if (ForeColor.HasValue)
                Console.ForegroundColor = ForeColor.Value;
            if (BackColor.HasValue)
                Console.BackgroundColor = BackColor.Value;
            string value = Console.ReadLine();
            return value;
        }

        private void ResetProperties()
        {
            Properties.Available.Arguments.Clear();
            Properties.Available.Options.Clear();
            Properties.Specified.Arguments.Clear();
            Properties.Specified.Options.Clear();
        }

        public void AddBuiltinCommand(params BuiltinCommands[] builtinCommands)
        {
            foreach (BuiltinCommands command in builtinCommands)
            {
                Tuple<Type, Func<InteractiveShell, CommandBase>> mapping = BuiltinCommandMapping.Mappings[command];
                _registeredCommands.Add(mapping.Item1, mapping.Item2 != null ? () => mapping.Item2(this) : (Func<CommandBase>)null);
            }
        }

        public void AddHelpSupport()
        {
            _registeredCommands.Add(() => new HelpCommand(_registeredCommands));
        }

        public string ExitKeyword
        {
            get { return _exitKeyword; }
            set { _exitKeyword = value ?? ShellMessages.ExitKeyword; }
        }

        public string Prompt
        {
            get { return _prompt; }
            set { _prompt = value ?? "> "; }
        }

        public ConsoleColor? PromptBackColor { get; set; }

        public ConsoleColor? PromptForeColor { get; set; }

        public ConsoleColor? BackColor { get; set; }

        public ConsoleColor? ForeColor { get; set; }

        public bool ClearScreenOnStart { get; set; }

        public bool ClearScreenOnEnd { get; set; }

        public bool SpaceBeforeCommand { get; set; }

        public bool SpaceAfterCommand { get; set; }

        public Action<InteractiveShell> OnStartup { get; set; }
    }
}
